package com.lock.redisLocks;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import com.lock.redisLocks.jedix.Jedix;
import com.lock.redisLocks.jedix.JedixLettuceClusterProxy;
import com.lock.redisLocks.jedix.JedixLettuceProxy;
import com.lock.redisLocks.jedix.JedixWrapping;
import com.lock.redisLocks.ledix.Ledix;
import com.lock.redisLocks.ledix.LedixWrapping;
import com.lock.redisLocks.support.RedisConfigHelper;
import com.lock.redisLocks.support.TwoTuple;

import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;

/**
 * @author wanghaitao
 */
@SuppressWarnings("rawtypes")
public class RedisHelper {

    private static Map<String, JedisPoolWraper> pools = new ConcurrentHashMap<>();
    private volatile static String DEFAULT_POOL_NAME = null;
    private volatile static boolean isShutdown = false;

    static {
        initRedisPool();
    }

    /**
     * 根据指定属性创建连接池实例.
     */
    private static void initRedisPool() {
        TwoTuple<String, Map<String, JedisPoolWraper>> tuple = RedisConfigHelper.initConfig();
        DEFAULT_POOL_NAME = tuple.getItem1();
        pools = tuple.getItem2();
        isShutdown = false;
    }

    public static Ledix getLedix() {
        return getLedix(DEFAULT_POOL_NAME);
    }

    @SuppressWarnings("unchecked")
    public static Ledix getLedix(String poolName) {
        assistCheckArgument(!isShutdown, "JedisHelper is shutdown");
        assistCheckArgument(StringUtils.isNotBlank(poolName), "PoolName should not be empty");

        JedisPoolWraper poolWraper = pools.get(poolName);
        assistCheckState(poolWraper != null, "Redis Resource " + poolName + " is null");

        ProviderConfigType providerConfigType = poolWraper.getProvider();

        assistCheckState(ProviderConfigType.LETTUCE.equals(providerConfigType), "Only Support LETTUCE Provider Operation, pool[" + poolName + "] provider is illegal");

        Ledix ledix = null;
        if (RedisConfigType.SINGLE.equals(poolWraper.getType())) {// LETTUCE单机
            StatefulRedisConnection<String, String> conn = (StatefulRedisConnection<String, String>) poolWraper.getPool();
            RedisCommands<String, String> syncCmd = conn.sync();
            ledix = LedixWrapping.<Ledix, RedisCommands<String, String>>wrapLedix(syncCmd, poolWraper.getType(), poolWraper.getProvider());

        } else if (RedisConfigType.CLUSTER.equals(poolWraper.getType())) {// LETTUCE集群
            StatefulRedisClusterConnection<String, String> conn = (StatefulRedisClusterConnection<String, String>) poolWraper.getPool();
            RedisAdvancedClusterCommands<String, String> syncCmd = conn.sync();
            ledix = LedixWrapping.<Ledix, RedisAdvancedClusterCommands<String, String>>wrapLedix(syncCmd, poolWraper.getType(), poolWraper.getProvider());

        } else {
            throw new UnsupportedOperationException("NOT Implemeted yet");
        }

        return ledix;
    }

    /**
     * 获取Jedis实例
     * 推荐使用getLedix
     *
     * @return
     */
    @Deprecated
    public static Jedix getJedix() {
        return getJedix(DEFAULT_POOL_NAME);
    }

    @SuppressWarnings("unchecked")
    @Deprecated
    public static Jedix getJedix(String poolName) {
        assistCheckArgument(!isShutdown, "JedisHelper is shutdown");
        assistCheckArgument(StringUtils.isNotBlank(poolName), "PoolName should not be empty");

        JedisPoolWraper jedisPoolWraper = pools.get(poolName);
        assistCheckState(jedisPoolWraper != null, "JedisPool " + poolName + " is null");
        if (ProviderConfigType.LETTUCE.equals(jedisPoolWraper.getProvider())) {
            if (RedisConfigType.SINGLE.equals(jedisPoolWraper.getType())) {// LETTUCE单机

                StatefulRedisConnection<String, String> connection = (StatefulRedisConnection<String, String>) jedisPoolWraper.getPool();
                return new JedixLettuceProxy(connection, jedisPoolWraper.isUsePool(), jedisPoolWraper.getType(), jedisPoolWraper.getProvider());

            } else if (RedisConfigType.CLUSTER.equals(jedisPoolWraper.getType())) {// LETTUCE集群
                StatefulRedisClusterConnection<String, String> connection = null;
                if (jedisPoolWraper.isUsePool()) {
                    try {
                        GenericObjectPool<StatefulRedisClusterConnection<String, String>> pool = (GenericObjectPool<StatefulRedisClusterConnection<String, String>>) jedisPoolWraper.getPool();
                        connection = pool.borrowObject();
                    } catch (Exception e) {
                        throw new IllegalArgumentException(e);
                    }
                } else {
                    connection = (StatefulRedisClusterConnection<String, String>) jedisPoolWraper.getPool();
                }
                return new JedixLettuceClusterProxy(connection, jedisPoolWraper.isUsePool(), jedisPoolWraper.getType(), jedisPoolWraper.getProvider());

            } else {
                throw new UnsupportedOperationException("NOT Implemeted yet");
            }
        } else if (ProviderConfigType.JEDIS.equals(jedisPoolWraper.getProvider())) {

            if (RedisConfigType.SINGLE.equals(jedisPoolWraper.getType())) {// JEDIS单机
                JedisPool jedisPool = (JedisPool) jedisPoolWraper.getPool();
                assistCheckState(!jedisPool.isClosed(), "JedisPool " + poolName + " has closed");

                Jedis jedis = jedisPool.getResource();
                assistCheckState(jedis != null, "Jedis[" + poolName + "] is null");
                assistCheckState(jedis.isConnected(), "Jedis[ " + poolName + "] not connected");

                // 动态代理, 实现IJedix接口, 返回
                Jedix intfJedix = JedixWrapping.<Jedix, Jedis>wrap(jedis, jedisPoolWraper.getType(), jedisPoolWraper.getProvider());
                return intfJedix;

            } else if (RedisConfigType.CLUSTER.equals(jedisPoolWraper.getType())) {// JEDIS集群
                JedisCluster jedisCluster = (JedisCluster) jedisPoolWraper.getPool();
                assistCheckState(jedisCluster != null, "JedisCluster[" + poolName + "] is null");
                // 动态代理, 实现IJedix接口, 返回
                Jedix intfJedix = JedixWrapping.<Jedix, JedisCluster>wrap(jedisCluster, jedisPoolWraper.getType(), jedisPoolWraper.getProvider());
                return intfJedix;

            } else {
                throw new UnsupportedOperationException("NOT Implemeted yet");
            }
        } else {
            throw new UnsupportedOperationException("NOT Implemeted yet, ProviderConfigType:" + jedisPoolWraper.getProvider() + ", RedisConfigType:" + jedisPoolWraper.getType());
        }
    }

    /**
     * 单纯获取单一模式Redis
     *
     * @return
     */
    @Deprecated
    public static Jedis getJedis() {
        return getJedis(DEFAULT_POOL_NAME);
    }

    @Deprecated
    public static Jedis getJedis(String name) {
        assistCheckArgument(!isShutdown, "JedisHelper is shutdown");
        assistCheckArgument(StringUtils.isNotBlank(name), "PoolName should not be empty");

        JedisPoolWraper jedisPoolWraper = pools.get(name);
        assistCheckState(jedisPoolWraper != null, "JedisPool " + name + " is null");
        if (RedisConfigType.SINGLE.equals(jedisPoolWraper.getType())) {
            JedisPool jedisPool = (JedisPool) jedisPoolWraper.getPool();
            assistCheckState(!jedisPool.isClosed(), "JedisPool " + name + " has closed");

            Jedis jedis = jedisPool.getResource();
            assistCheckState(jedis != null, "Jedis[" + name + "] is null");
            assistCheckState(jedis.isConnected(), "Jedis[ " + name + "] not connected");

            return jedis;
        }
        throw new RuntimeException("POOL[" + name + "] pool-type must be SINGLE");

    }

    public static JedisPoolWraper getJedisPoolWraper(String name) {
        assistCheckArgument(!isShutdown, "JedisHelper is shutdown");
        assistCheckArgument(StringUtils.isNotBlank(name), "PoolName should not be empty");
        return pools.get(name);
    }

    public static synchronized void shutdown() {
        assistCheckArgument(!isShutdown, "JedisHelper is shutdown");
        try {
            RedisConfigHelper.clearPool(pools);
        } finally {
            pools = null;
            isShutdown = true;
        }
    }

    public static boolean isShutdown() {
        return isShutdown;
    }

    public static String getDefaultPoolName() {
        return DEFAULT_POOL_NAME;
    }

    public static Set<String> getPoolNames() {
        assistCheckArgument(!isShutdown, "JedisHelper is shutdown");
        return pools.keySet();
    }

    public static String getPoolType(String name) {
        assistCheckArgument(!isShutdown, "JedisHelper is shutdown");
        assistCheckArgument(StringUtils.isNotBlank(name), "PoolName should not be empty");
        return pools.get(name).getType().getType();
    }

    public static class JedisPoolWraper<T, E, V> {
        private final String name;
        private T pool;
        private final boolean usePool;
        private final RedisConfigType type;
        private final ProviderConfigType provider;
        private final GenericObjectPoolConfig<?> poolConfig;
        private final Iterable<E> redisServerSet;
        private final V appendObj;

        public JedisPoolWraper(final String name, final T pool, boolean usePool, final RedisConfigType type, final ProviderConfigType provider, final GenericObjectPoolConfig poolConfig, Iterable<E> redisServerSet) {
            this(name, pool, usePool, type, provider, poolConfig, redisServerSet, null);
        }

        public JedisPoolWraper(final String name, final T pool, boolean usePool, final RedisConfigType type, final ProviderConfigType provider, final GenericObjectPoolConfig poolConfig, Iterable<E> redisServerSet, V appendObj) {
            super();
            this.name = name;
            this.pool = pool;
            this.usePool = usePool;
            this.type = type;
            this.provider = provider;
            this.poolConfig = poolConfig;
            this.redisServerSet = redisServerSet;
            this.appendObj = appendObj;
        }

        public String getName() {
            return name;
        }

        public T getPool() {
            return pool;
        }

        public RedisConfigType getType() {
            return type;
        }

        public GenericObjectPoolConfig<?> getPoolConfig() {
            return poolConfig;
        }

        /**
         * @return the provider
         */
        public ProviderConfigType getProvider() {
            return provider;
        }

        /**
         * @return the redisServerSet
         */
        public Iterable<E> getRedisServerSet() {
            return redisServerSet;
        }

        /**
         * @return the usePool
         */
        public boolean isUsePool() {
            return usePool;
        }

        /**
         * @return the appendObj
         */
        public V getAppendObj() {
            return appendObj;
        }

    }

    public static void assistCheckState(boolean expression, Object errorMessage) {
        if (!expression) {
            throw new IllegalStateException(String.valueOf(errorMessage));
        }
    }

    public static void assistCheckArgument(boolean expression, Object errorMessage) {
        if (!expression) {
            throw new IllegalArgumentException(String.valueOf(errorMessage));
        }
    }

    // Provider类型,
    static public enum ProviderConfigType {
        NULL(null, -1), //
        JEDIS("JEDIS", 1), //
        LETTUCE("LETTUCE", 2);
        public int code;
        public String type;

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        ProviderConfigType(String type, int code) {
            this.type = type;
            this.code = code;
        }

        public int getCode() {
            return code;
        }

        public void setCode(int code) {
            this.code = code;
        }

        public static ProviderConfigType getFromType(String type) {
            for (ProviderConfigType typeEnum : ProviderConfigType.values()) {
                if (StringUtils.equalsIgnoreCase(typeEnum.getType(), type)) {
                    return typeEnum;
                }
            }
            throw new IllegalArgumentException("Invalid ProviderConfigType:" + type);
        }
    }

    // Redis类型,
    static public enum RedisConfigType {

        // NULL(null, -1), //
        SINGLE("SINGLE", 0), //
        CLUSTER("CLUSTER", 1); //
        // SENTINEL("SENTINEL", 2);
        public int code;
        public String type;

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        RedisConfigType(String type, int code) {
            this.type = type;
            this.code = code;
        }

        public int getCode() {
            return code;
        }

        public void setCode(int code) {
            this.code = code;
        }

        public static RedisConfigType getFromType(String type) {
            for (RedisConfigType typeEnum : RedisConfigType.values()) {
                if (StringUtils.equalsIgnoreCase(typeEnum.getType(), type)) {
                    return typeEnum;
                }
            }
            throw new IllegalArgumentException("Invalid RedisConfigType:" + type);
        }
    }

}
